How it's wired Tepna
← the -Dex suite · node wiring

🩸OxyDex

OxyDex is the suite’s oxygen receptor — it senses one signal, blood-oxygen saturation, and reports it inward. From a raw overnight 1 Hz SpO₂/HR/motion trace it derives 75+ metrics, scores recovery readiness, and emits desaturation events onto the Ganglior bus.

Signal sensed
SpO₂ + pulse rate + motion1 Hz overnight trace
Device
O2Ring / Wellue / ViATOMfingertip-free pulse oximeter
Input format
Raw 1 Hz CSV+ summary CSV · JSONL re-import
Produces
75+ metrics · readiness→ ganglior.node-export
01 — WHAT IT'S FOR

A single-signal receptor

Read the suite as a reflex arc: receptors → relay → integration → insight. OxyDex is an afferent receptor — it does exactly one transduction well and reports inward. It never reaches into another node; cross-signal work happens only through the Ganglior export, consumed by the Integrator.

🧠
One signal per node. OxyDex senses spo₂ + pulse rate + motion and nothing else. If you’re tempted to sense a second signal, that’s a second node — fusion is the Integrator’s job, never a receptor’s.
02 — THE WIRING

The file stack

OxyDex is built from external *.js files referenced by OxyDex.src.html and bundled to a standalone OxyDex.html. Dependencies point downhill only — UI → DSP → Core — and each file owns exactly one job. Edit the .js + .src.html, never the bundle; re-bundle after changes.

DSP
oxydex-util.jsDSP
Tiny shared math / formatting helpers, loaded first so every later file can use them.
oxydex-profile.jsDSP
User profile (age/sex/fitness) in localStorage, population norms, and the age-relative readiness framing.
oxydex-dsp.jsDSP
The whole analysis engine: CSV/JSONL parsing, artifact cleaning, and every metric tier — SpO₂, ODI, T90, hypoxic burden, pulse-HRV proxies, DFA/FFT/entropy, composites, VO₂max/BP.
Core
kernel-constants.jsCore · shared
The frozen physiology kernel — one source of truth for every cross-fleet threshold, plus a content-derived hash stamped into each export so two bundles built on different rulebooks can’t silently “agree” when fused.
ganglior-provenance.jsCore · shared
Loaded first — installs the read-hook that makes every export attributable to the exact code + inputs that produced it (the buildHash provenance stamp). 100% local.
metric-registry.jsCore · shared
The shared half of the evidence system: the badge SHAPE, depth tiering and the injected badge CSS. The per-node registry below supplies the DATA, so a Validated dot looks identical in every node.
crossnight-envelope.jsCore · shared
The shared, versioned shape (ganglior.crossnight v1.0) every node emits and the Integrator reads — the container, not the math (the local crossNight() stats stay per-node).
oxydex-registry.jsCore
Per-metric DATA map: label, unit, goodDirection, depth tier and evidence grade. The one place each OxyDex metric is described.
Render · UI
oxydex-render.jsRender · UI
All DOM + chart drawing: readiness hero, KPI strip, night rows, the SpO₂ trace, tables. Asks DSP for numbers and the registry for how to label/badge them.
App · glue
oxydex-app.jsApp · glue
Loaded last — file ingest, progress, the mode (Core/Advanced/Research) and theme toggles, and the CSV/JSON exporters.
Fusion · export
oxydex-cross.jsFusion · export
Multi-night analytics: trend (OLS + Mann–Kendall), z-scores, bootstrap CIs across loaded nights. Byte-identical to every node’s cross helper.
oxydex-fusion.jsFusion · export
Ingests an ECGDex export to borrow real RR-based HRV, builds the Ganglior event stream, and writes the node-export JSON.
🕒
The Clock Contract. OxyDex stamps every record as floating wall-clock tMs (local civil time encoded as if UTC) and reads it back with getUTC* — so the recording reads identically in any viewer’s timezone, and two devices recording the same minute land on the same tMs. parseTimestamp is duplicated locally by design.
03 — HOW IT'S ACHIEVED

From file drop to Ganglior event

The pipeline is the same shape across the suite: ingest → parse on the canonical clock → compute in DSP → grade & render in UI → emit onto the bus. No number is computed in the render layer; it asks DSP for the value and the registry for how to show it.

Drop the file

Drag an O2Ring CSV (or summary/JSONL) onto the upload zone. Everything runs in-browser — nothing is uploaded.

oxydex-app.js → fileInput handler

Parse + stamp the clock

Each row’s vendor timestamp is parsed by regex into a floating wall-clock tMs — never new Date(str) — so the night reads identically in any timezone.

parseTimestamp() · O2Ring HH:MM:SS DD/MM/YYYY

Clean + compute

Artifacts are masked, then the metric tiers are computed on the full cleaned series: SpO₂ stats, ODI-4, T90, hypoxic burden, pulse-HRV proxies, composites.

oxydex-dsp.js → processNight()

Grade + render

Render asks the registry for each metric’s depth tier and evidence grade, draws the readiness hero, KPI strip, SpO₂ trace and tables.

oxydex-render.js + oxydex-registry.js

Emit onto Ganglior

Export a ganglior.node-export JSON — desaturation events with wall-clock t — for the Integrator to fuse with other nodes.

oxydex-fusion.js → buildExport()
04 — HOW MUCH TO TRUST IT

The evidence ladder

Every metric OxyDex surfaces carries an evidence grade — a non-hue badge whose fill is the confidence ladder: solid bullseye for a raw reading, down to a dashed ring for a friendly estimate. A measurement and a projection must never look alike. Grades live in oxydex-registry.js, the one source of truth.

Measured Mean / min SpO₂ · nadir · mean HR · recording duration · % analyzable
Validated ODI-4 (AASM) · T90 · hypoxic burden (Azarbarzin 2019)
Emerging CVHR apnea screen · DFA α1 · mean-HR dip
Experimental NSI · sleep-stability · SBII — OxyDex internal composites
Heuristic ANS-age · VO₂max estimate · BP projection
05 — LIMITATIONS & CONSIDERATIONS

What it can and can't say

Honesty is architectural: a guess is never dressed as a measurement, a missing value stays null rather than fabricated, and confidence is kept separate from signal quality. The node-specific caveats:

Pulse-rate HRV is a proxylimitation

OxyDex sees pulse rate at 1 Hz, not beat-to-beat RR. Its HRV-flavoured metrics are emerging, never validated — for real HRV magnitude it borrows an ECGDex/PulseDex export.

CVHR is a screen, not a diagnosiscaution

The cyclic-variation-of-heart-rate apnea signal flags pattern, not an AHI. It directs attention; it does not diagnose sleep apnea.

Motion artifactsnote

Finger/ring movement injects spurious dips. The cleaner masks the worst, and % analyzable is surfaced so a low-coverage night reads as low-confidence.

Honest about absenceby design

A missing timestamp stays null — never fabricated to “now”. Coverage gaps are visible rather than smoothed away.

06 — HOW TO USE IT

For the curious, and for the next coder

📂

Open the instrument

OxyDex is one self-contained file. Open OxyDex.html in any browser — online, offline, or from file://. Nothing installs, nothing phones home.

Open OxyDex →
📥

Drop your recording

Drag a O2Ring / Wellue / ViATOM export (raw 1 hz csv) onto the upload zone. It’s read in-browser and turned into metrics on your device alone.

🎚️

Read at your depth

Start in Core for the headline readiness and a plain-language line. Step up to Advanced for full tables and charts, or Research for composites and the full dump. Each metric’s evidence dot tells you how far to trust it.

🔀

Export onto Ganglior

Export a ganglior.node-export JSON to feed the Integrator, or to upgrade a peer node’s read. Events carry a wall-clock t; consumers reconstruct absolute time from the recording start.

OxyDex reference guide →
🛠️

If you’re changing it

Edit the oxydex-*.js files or OxyDex.src.htmlnever the bundled OxyDex.html — then re-bundle. Run Dex-Test-Suite.html until all-green, and verify-provenance.html after re-bundling.